classdef Control < handle
    properties
        Body % Collection of code segments found at the same level.
    end
    
    
    
    
    methods
        function this = Control(c, wh, sh)
            import preparser.control.*;
            if nargin==0
                return
            end
            try, sh; catch, sh = Control.createShadowCode(wh); end %#ok<VUNUS,NOCOM>
            [c, sh] = Control.handleStop(c, sh); % Remove everything after first !stop.
            body = { };
            while true
                posNextKey = find(real(sh)>0, 1);
                if isempty(posNextKey)
                    body{end+1} = c; %#ok<AGROW>
                    break
                end
                body{end+1} = c(1:posNextKey-1); %#ok<AGROW>
                c = c(posNextKey:end);
                sh = sh(posNextKey:end);
                if imag(sh(1))==0
                    [body, c, sh] = Control.handleInline(body, c, sh);
                else
                    [body, c, sh] = Control.handleBlock(body, c, sh);
                end
                if isempty(c)
                    break
                end
            end
            this.Body = body;
        end
        
        
        
        
        function c = writeFinal(this,p,varargin)
            import preparser.control.Control;
            c = '';
            n = numel(this.Body);
            for i = 1 : n
                g = this.Body{i};
                if ischar(g)
                    g = Control.substituteForCtrl(g, p);
                    c = [ c, g ]; %#ok<AGROW>
                else
                    c = [ c, writeFinal(g,p,varargin{:}) ]; %#ok<AGROW>
                end
            end
        end
        
        
        
        
        function flag = isempty(this)
            flag = isempty(this.Body) || ...
                ( ...
                iscellstr(this.Body) ...
                && all(cellfun(@isempty,this.Body)) ...
                );
        end
    end
    
    
    
    
    methods (Static)
        function parse(p)
            import preparser.control.*;
            this = Control(p.Code, p.White);
            c = writeFinal(this, p);
            p.Code = c;
            p.White = [ ];
        end
        
        
        
        
        function sh = createShadowCode(wh)
            keywordEnum = enumeration('preparser.control.Keyword');
            sh = zeros(1,length(wh),'int8');
            for i = 1 : numel(keywordEnum)
                x = keywordEnum(i);
                ix = strfind(wh,toChar(x));
                if ~isempty(ix)
                    sh(ix) = int8(x);
                    wh(ix) = char(0);
                end
            end
        end
        
        
        
        
        function [c, sh] = handleStop(c, sh)
            pos = find(real(sh)==preparser.control.Keyword.STOP, 1);
            if ~isempty(pos)
                c = c(1:pos-1);
                sh = sh(1:pos-1);
            end            
        end
        
        
        
        
        function [body, c, sh] = handleInline(body, c, sh)
            import preparser.control.*;
            key = Keyword(sh(1));
            switch key
                case { Keyword.IMPORT, Keyword.INCLUDE, Keyword.INPUT }
                    [arg, c, sh] = Import.getBracketArg(key, c, sh);
                    body{end+1} = Import(arg);
                case { Keyword.RETURN }
                    c = c([ ]);
                    sh = sh([ ]);
                otherwise
                    throw(exception.Preparser.INTERNAL);
            end
        end
        
        
        
        
        function [body, c, sh] = handleBlock(body, c, sh)
            import preparser.control.*;
            keyOpen = Keyword(sh(1));
            level = cumsum(imag(sh));
            close = find(level==0, 1);
            if isempty(close)
                throwCode(exception.Preparser.CTRL_MISSING_END, c);
            end
            c1 = c(1:close-1);
            sh1 = sh(1:close-1);
            switch keyOpen
                case Keyword.EXPORT
                    body{end+1} = Export(c1, sh1);
                case Keyword.FOR
                    body{end+1} = For(c1, sh1);
                case Keyword.IF
                    body{end+1} = If(c1, sh1);
                case Keyword.SWITCH
                    body{end+1} = Switch(c1, sh1);
                otherwise
                    throw(exception.Preparser.INTERNAL);                    
            end
            keyClose = Keyword(sh(close));
            c = c(close+len(keyClose):end);
            sh = sh(close+len(keyClose):end);
        end

        
        
        
        function c = substituteForCtrl(c, p)
            import preparser.control.Control;
            if isempty(strfind(c, '?'))
                return
            end
            for i = 1 : size(p.StoreForCtrl,1)
                ctrlName = p.StoreForCtrl{i,1};
                token = p.StoreForCtrl{i,2};
                Control.chkObsoleteUpperLower(c, ctrlName);
                if length(ctrlName)>1
                    upperCtrlName = [ctrlName(1),':',ctrlName(2:end)];
                    upperToken = upper(token);
                    lowerCtrlName = [ctrlName(1),'.',ctrlName(2:end)];
                    lowerToken = lower(token);
                    % Substitute lower case for for ?.name.
                    c = strrep(c,lowerCtrlName,lowerToken);
                    % Substitute upper case for for ?:name.
                    c = strrep(c,upperCtrlName,upperToken);
                end
                % Substitute for ?name.
                c = strrep(c,ctrlName,token);
            end
        end
        
        
        
        
        function chkObsoleteUpperLower(c, controlName)
            obsoleteFunc = @(syntax) ...
                regexp(c, regexptranslate('escape',syntax), 'match');
            listObsolete = [ ...
                obsoleteFunc([ '!lower',  controlName       ]), ...
                obsoleteFunc([ '!upper',  controlName       ]), ...
                obsoleteFunc([ '<lower(', controlName, ')>' ]), ...
                obsoleteFunc([ '<upper(', controlName, ')>' ]), ...
                obsoleteFunc([ 'lower(',  controlName, ')'  ]), ...
                obsoleteFunc([ 'upper(',  controlName, ')'  ]), ...
                obsoleteFunc([ '<-',      controlName, '>'  ]), ...
                obsoleteFunc([ '<+',      controlName, '>'  ]), ...
                obsoleteFunc([ '<',       controlName, '>'  ]), ...
                ];
            if isempty(listObsolete)
                return
            end
            listObsolete = unique(listObsolete);
            throwCode( exception.Preparser.CTRL_OBSOLETE_UPPER_LOWER, ...
                listObsolete{:} );
        end        
    end
end
